package com.kuangqi.paymentdemo.service.impl;

import com.google.gson.Gson;
import com.kuangqi.paymentdemo.config.WxPayConfig;
import com.kuangqi.paymentdemo.entity.OrderInfo;
import com.kuangqi.paymentdemo.entity.RefundInfo;
import com.kuangqi.paymentdemo.enums.OrderStatus;

import com.kuangqi.paymentdemo.enums.wxpay.WxApiType;
import com.kuangqi.paymentdemo.enums.wxpay.WxNotifyType;
import com.kuangqi.paymentdemo.enums.wxpay.WxTradeState;
import com.kuangqi.paymentdemo.service.OrderInfoService;
import com.kuangqi.paymentdemo.service.PaymentInfoService;
import com.kuangqi.paymentdemo.service.RefundInfoService;
import com.kuangqi.paymentdemo.service.WeChatpayService;
import com.kuangqi.paymentdemo.util.OrderNoUtils;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;


/**
 * @author kuanggong
 */
@Service
@Slf4j
public class WeChatpayServiceImpl implements WeChatpayService {
    @Autowired
    WxPayConfig wxPayConfig;
    @Autowired
    CloseableHttpClient httpClient;
    @Autowired
    CacheService cacheService;
    @Autowired
    OrderInfoService orderInfoService;
    @Autowired
    PaymentInfoService paymentInfoService;
    @Autowired
    RedissonClient redissonClient;
    @Autowired
    RefundInfoService refundInfoService;
    @Resource
    //无需应答签名
    private CloseableHttpClient wxPayNoSignClient;
    /**
     * 可重入锁
     */


    /**
     * 微信支付native下单功能
     *
     * @param productId
     * @return
     */
    @SneakyThrows
    @Override
    public Map<String, Object> nativePay(Long productId) {
        String codeUrl;
        OrderInfo orderInfo = orderInfoService.createInfo(productId);
        if (cacheService.exists("code_url")) {
            codeUrl = cacheService.get("code_url");
        } else {
            //(二)调用统一下单API
            HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.NATIVE_PAY.getType()));
            //使用gson封装请求体
            Gson gson = new Gson();
            //使用链表hash
            LinkedHashMap<Object, Object> linkedHashMap = new LinkedHashMap<>();
            linkedHashMap.put("appid", wxPayConfig.getAppid());
            linkedHashMap.put("mchid", wxPayConfig.getMchId());
            linkedHashMap.put("description", orderInfo.getTitle());
            linkedHashMap.put("out_trade_no", orderInfo.getOrderNo());
            linkedHashMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.NATIVE_NOTIFY.getType()));
            HashMap<Object, Object> amountMap = new HashMap<>(2);
            amountMap.put("total", orderInfo.getTotalFee());
            amountMap.put("currency", "CNY");
            linkedHashMap.put("amount", amountMap);
            String jsonParams = gson.toJson(linkedHashMap);

            log.info("下单参数:" + jsonParams);

            StringEntity entity = new StringEntity(jsonParams, "utf8");
            entity.setContentType("application/json");

            httpPost.setEntity(entity);
            httpPost.setHeader("Accept", "application/json");

            CloseableHttpResponse response = httpClient.execute(httpPost);
            //返回状态码
            int statusCode = response.getStatusLine().getStatusCode();
            String responseEntity = EntityUtils.toString(response.getEntity());
            switch (statusCode) {
                case 200:
                    log.info("200响应成功:[{}]", responseEntity);
                    break;
                case 204:
                    log.info("204相应成功,无返回状态:");
                default:
                    log.error("出错了,响应码:[{}],返回对象:[{}]", statusCode, responseEntity);
                    break;
            }
            //相应结果
            Map<String, String> resultMap = gson.fromJson(responseEntity, HashMap.class);
            //解析二维码
            codeUrl = resultMap.get("code_url");
            cacheService.setEx("code_url", codeUrl, 100, TimeUnit.MINUTES);
            httpClient.close();
        }

        Map<String, Object> returnMap = new HashMap<>(2);
        returnMap.put("codeUrl", codeUrl);
        returnMap.put("orderNo", orderInfo.getOrderNo());
        return returnMap;
    }


    /**
     * 微信支付jsapi预下单功能
     *
     * @param params
     * @return
     */
    @SneakyThrows
    @Override
    public String jsapiPay(Map<String, Object> params) {

        OrderInfo orderInfo = new OrderInfo();
        orderInfo.setTitle(params.get("title").toString());
        //算法生成订单
        orderInfo.setOrderNo(OrderNoUtils.getOrderNo());
        orderInfo.setProductId(Long.parseLong(params.get("productId").toString()));
        //单位是分
        orderInfo.setTotalFee(Integer.parseInt(params.get("totalFee").toString()));
        //订单状态
        orderInfo.setOrderStatus(OrderStatus.NOTPAY.getType());
        HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.JSAPI_PREPAY.getType()));
        Gson gson = new Gson();
        Map<Object, Object> linkedHashMap = new LinkedHashMap<>();
        linkedHashMap.put("appid", wxPayConfig.getAppid());

        linkedHashMap.put("mchid", wxPayConfig.getMchId());

        linkedHashMap.put("description", orderInfo.getTitle());

        linkedHashMap.put("out_trade_no", orderInfo.getOrderNo());

        linkedHashMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.JSAPI_NOTIFY.getType()));

        HashMap<Object, Object> amountMap = new HashMap<>(2);
        amountMap.put("total", orderInfo.getTotalFee());
        amountMap.put("currency", "CNY");
        linkedHashMap.put("amount", amountMap);
        //获取使用该小程序/app/公众号的微信用户
        HashMap<String, String> payerMap = new HashMap<>(1);
        payerMap.put("openid", params.get("openId").toString());
        linkedHashMap.put("payer", payerMap);

        String jsonParams = gson.toJson(linkedHashMap);

        log.info("下单参数:" + jsonParams);
        StringEntity entity = new StringEntity(jsonParams, "utf8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");

        CloseableHttpResponse response = httpClient.execute(httpPost);
        //返回状态码
        try {
            int statusCode = response.getStatusLine().getStatusCode();
            String responseEntity = EntityUtils.toString(response.getEntity());
            switch (statusCode) {
                case 200:
                    log.info("200响应成功:[{}]", responseEntity);
                    cacheService.setEx("prepay_id", responseEntity, 100, TimeUnit.MINUTES);
                    break;
                case 204:
                    log.info("204响应成功,无返回状态:");
                    break;
                default:
                    log.error("出错了,响应码:[{}],返回对象:[{}]", statusCode, responseEntity);
                    break;
            }
            return responseEntity;
        } finally {
            response.close();
        }
    }

    /**
     * 处理订单
     *
     * @param bodyMap
     */
    @Override
    public void processOrder(Map<String, Object> bodyMap) {
        RLock mylock = redissonClient.getLock("paylock");
        log.info("处理订单");
        String plainText = decryptFromResource(bodyMap);
        //获取明文,处理订单
        Gson gson = new Gson();
        Map plainTextMap = gson.fromJson(plainText, HashMap.class);
        String orderNo = plainTextMap.get("out_trade_no").toString();

        //分布式锁解决支付并发问题
        //如果获取锁返回TRUE,否则返回FALSE  不必等待锁的释放 (不同于sync锁)
        if (mylock.tryLock()) {

            try {
                //判断是否已经处理过该订单
                if (cacheService.exists(orderNo) || !cacheService.get(orderNo).equals(OrderStatus.SUCCESS.getType()))
                    return;
                //更新订单状态
                orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.SUCCESS);
                //记录支付日志
                paymentInfoService.createPaymentInfo(plainText);
                cacheService.setEx(orderNo, WxTradeState.SUCCESS.getType(), 24, TimeUnit.HOURS);
            } finally {
                mylock.unlock();
            }

        }
    }


    /**
     * 解密,获取支付回调参数
     *
     * @param bodyMap
     * @return
     */
    @SneakyThrows
    private String decryptFromResource(Map<String, Object> bodyMap) {
        //获得解密工具
        AesUtil aesUtil = new AesUtil(wxPayConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));
        //所需要的三个参数来自bodyMap,分别是 关联数据,随机串,密文
        // byte[] associatedData, byte[] nonce, String ciphertext

        Map<String, String> resourceMap = (Map) bodyMap.get("resource");
        //获取数据密文
        String ciphertext = resourceMap.get("ciphertext");
        //获取随机串
        String nonce = resourceMap.get("nonce");
        //获取附加数据
        String associated_data = resourceMap.get("associated_data");
        //获取明文对象
        String plaintextObj = aesUtil.decryptToString(associated_data.getBytes(StandardCharsets.UTF_8), nonce.getBytes(StandardCharsets.UTF_8), ciphertext);
        log.info("获取到的明文对象==>[{}]", plaintextObj);
        return plaintextObj;
    }


    /**
     * 取消订单
     *
     * @param orderNo
     */
    @Override
    public void cancelOrder(String orderNo) {
        //调用微信支付关单接口
        this.closeOrder(orderNo);
        //更新商户端的订单状态
        orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.CANCEL);
    }

    /**
     * 订单查询
     *
     * @param orderNo
     * @return
     */
    @Override
    @SneakyThrows
    public String queryOrder(String orderNo) {
        log.info("查单接口调用,订单号=====>{}", orderNo);
        String url = String.format(WxApiType.ORDER_QUERY_BY_NO.getType(), orderNo);
        url = wxPayConfig.getDomain().concat(url).concat("?mchid=").concat(wxPayConfig.getMchId());

        HttpGet httpGet = new HttpGet(url);
        httpGet.setHeader("Accept", "application/json");
        CloseableHttpResponse response = httpClient.execute(httpGet);
        try {
            int statusCode = response.getStatusLine().getStatusCode();
            String responseEntity = EntityUtils.toString(response.getEntity());
            switch (statusCode) {
                case 200:
                    log.info("200响应成功:[{}]", responseEntity);
                    break;
                case 204:
                    log.info("204响应成功,无返回状态:");
                    break;
                default:
                    log.error("出错了,响应码:[{}],返回对象:[{}]", statusCode, responseEntity);
                    break;
            }
            return responseEntity;
        } finally {
            response.close();
        }
    }

    /**
     * 根据订单号通过查单接口查询订单状态,
     * 如果已支付,则修改我方订单状态为已支付,
     * 如果未支付,则关闭订单。
     *
     * @param orderNo
     */
    @Override
    public void checkOrderStatus(String orderNo) {
        log.warn("根据订单号核实订单状态======>{}", orderNo);
        String result = this.queryOrder(orderNo);
        Gson gson = new Gson();
        Map hashMap = gson.fromJson(result, HashMap.class);

        Object trade_state = hashMap.get("trade_state");
        if (WxTradeState.SUCCESS.getType().equals(trade_state)) {
            log.warn("核实订单已支付=======>{}", orderNo);

            orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.SUCCESS);
            //记录支付日志
            paymentInfoService.createPaymentInfo(result);
        }
        if (WxTradeState.NOTPAY.getType().equals(trade_state)) {
            log.warn("核实订单未支付======>{}", orderNo);
            this.closeOrder(orderNo);
            orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.CLOSED);

        }

    }

    /**
     * 退款
     *
     * @param orderNo
     * @param reason
     * @throws IOException
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    @SneakyThrows
    public void refund(String orderNo, String reason) {

        log.info("创建退款单记录");
        //根据订单编号创建退款单
        RefundInfo refundsInfo = refundInfoService.createRefundByOrderNo(orderNo, reason);

        log.info("调用退款API");

        //调用统一下单API
        String url = wxPayConfig.getDomain().concat(WxApiType.DOMESTIC_REFUNDS.getType());
        HttpPost httpPost = new HttpPost(url);

        // 请求body参数
        Gson gson = new Gson();
        Map paramsMap = new HashMap();
        paramsMap.put("out_trade_no", orderNo);//订单编号
        paramsMap.put("out_refund_no", refundsInfo.getRefundNo());//退款单编号
        paramsMap.put("reason", reason);//退款原因
        paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.REFUND_NOTIFY.getType()));

        Map amountMap = new HashMap();
        amountMap.put("refund", refundsInfo.getRefund());//退款金额
        amountMap.put("total", refundsInfo.getTotalFee());//原订单金额
        amountMap.put("currency", "CNY");//退款币种
        paramsMap.put("amount", amountMap);

        //将参数转换成json字符串
        String jsonParams = gson.toJson(paramsMap);
        log.info("请求参数 ===> {}" + jsonParams);

        StringEntity entity = new StringEntity(jsonParams, "utf-8");
        entity.setContentType("application/json");//设置请求报文格式
        httpPost.setEntity(entity);//将请求报文放入请求对象
        httpPost.setHeader("Accept", "application/json");//设置响应报文格式

        //完成签名并执行请求，并完成验签
        CloseableHttpResponse response = httpClient.execute(httpPost);

        try {

            //解析响应结果
            String bodyAsString = EntityUtils.toString(response.getEntity());
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                log.info("成功, 退款返回结果 = " + bodyAsString);
            } else if (statusCode == 204) {
                log.info("成功");
            } else {
                throw new RuntimeException("退款异常, 响应码 = " + statusCode + ", 退款返回结果 = " + bodyAsString);
            }

            //更新订单状态
            orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.REFUND_PROCESSING);

            //更新退款单
            refundInfoService.updateRefund(bodyAsString);

        } finally {
            response.close();
        }
    }


    /**
     * 微信native支付关单功能
     *
     * @param orderNo
     */
    @SneakyThrows
    private void closeOrder(String orderNo) {
        log.info("关单接口的调用,订单号=====>[{}]", orderNo);
        String url = String.format(WxApiType.CLOSE_ORDER_BY_NO.getType(), orderNo);
        url = wxPayConfig.getDomain().concat(url);
        HttpPost httpPost = new HttpPost(url);
        //组装json请求体
        Gson gson = new Gson();
        HashMap<String, String> paramMap = new HashMap<>();
        paramMap.put("mchid", wxPayConfig.getMchId());
        String s = gson.toJson(paramMap);
        log.info("请求参数====>[{}]", s);
        StringEntity entity = new StringEntity(s, "utf8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");
        CloseableHttpResponse response = httpClient.execute(httpPost);
        int statusCode = response.getStatusLine().getStatusCode();
        String responseEntity = EntityUtils.toString(response.getEntity());
        switch (statusCode) {
            case 200:
                log.info("响应成功");
                break;
            case 204:
                log.info("响应成功204");
                break;
            default:
                log.error("出错了,响应码:[{}],返回对象:[{}]", statusCode, responseEntity);
                break;
        }
        response.close();
    }
    /**
     * 查询退款接口调用
     * @param refundNo
     * @return
     */
    @Override
    @SneakyThrows
    public String queryRefund(String refundNo) {

        log.info("查询退款接口调用 ===> {}", refundNo);

        String url =  String.format(WxApiType.DOMESTIC_REFUNDS_QUERY.getType(), refundNo);
        url = wxPayConfig.getDomain().concat(url);

        //创建远程Get 请求对象
        HttpGet httpGet = new HttpGet(url);
        httpGet.setHeader("Accept", "application/json");

        //完成签名并执行请求
        CloseableHttpResponse response = httpClient.execute(httpGet);

        try {
            String bodyAsString = EntityUtils.toString(response.getEntity());
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == HttpStatus.SC_OK) {
                log.info("成功, 查询退款返回结果 = " + bodyAsString);
            } else if (statusCode == HttpStatus.SC_NO_CONTENT) {
                log.info("成功");
            } else {
                throw new RuntimeException("查询退款异常, 响应码 = " + statusCode+ ", 查询退款返回结果 = " + bodyAsString);
            }

            return bodyAsString;

        } finally {
            response.close();
        }
    }

    /**
     * 处理退款单
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    @SneakyThrows
    public void processRefund(Map<String, Object> bodyMap)  {
        RLock mylock = redissonClient.getLock("refundlock");
        log.info("退款单");

        //解密报文
        String plainText = decryptFromResource(bodyMap);

        //将明文转换成map
        Gson gson = new Gson();
        HashMap plainTextMap = gson.fromJson(plainText, HashMap.class);
        String orderNo = (String)plainTextMap.get("out_trade_no");

        if(mylock.tryLock()){
            try {

                String orderStatus = orderInfoService.getOrderStatus(orderNo);
                if (!OrderStatus.REFUND_PROCESSING.getType().equals(orderStatus)) {
                    return;
                }

                //更新订单状态
                orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.REFUND_SUCCESS);

                //更新退款单
                refundInfoService.updateRefund(plainText);

            } finally {
                //要主动释放锁
                mylock.unlock();
            }
        }
    }

    /**
     * 下载账单
     * @param billDate
     * @param type
     * @return
     * @throws Exception
     */
    @Override
    @SneakyThrows
    public String       downloadBill(String billDate, String type)  {
        log.warn("下载账单接口调用 {}, {}", billDate, type);

        //获取账单url地址
        String downloadUrl = this.queryBill(billDate, type);
        //创建远程Get 请求对象
        HttpGet httpGet = new HttpGet(downloadUrl);
        httpGet.addHeader("Accept", "application/json");

        //使用wxPayClient发送请求得到响应
        CloseableHttpResponse response = wxPayNoSignClient.execute(httpGet);

        try {

            String bodyAsString = EntityUtils.toString(response.getEntity());

            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == HttpStatus.SC_OK) {
                log.info("成功, 下载账单返回结果 = " + bodyAsString);
            } else if (statusCode == HttpStatus.SC_NO_CONTENT) {
                log.info("成功");
            } else {
                throw new RuntimeException("下载账单异常, 响应码 = " + statusCode+ ", 下载账单返回结果 = " + bodyAsString);
            }

            return bodyAsString;

        } finally {
            response.close();
        }
    }
    /**
     * 申请账单
     * @param billDate
     * @param type
     * @return
     *
     */
    @Override
    @SneakyThrows
    public String queryBill(String billDate, String type)  {
        log.warn("申请账单接口调用 {}", billDate);

        String url = "";
        if("tradebill".equals(type)){
            url =  WxApiType.TRADE_BILLS.getType();
        }else if("fundflowbill".equals(type)){
            url =  WxApiType.FUND_FLOW_BILLS.getType();
        }else{
            throw new RuntimeException("不支持的账单类型");
        }

        url = wxPayConfig.getDomain().concat(url).concat("?bill_date=").concat(billDate);

        //创建远程Get 请求对象
        HttpGet httpGet = new HttpGet(url);
        httpGet.addHeader("Accept", "application/json");

        //使用wxPayClient发送请求得到响应
        CloseableHttpResponse response = httpClient.execute(httpGet);

        try {

            String bodyAsString = EntityUtils.toString(response.getEntity());

            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == HttpStatus.SC_OK) {
                log.info("成功, 申请账单返回结果 = " + bodyAsString);
            } else if (statusCode == HttpStatus.SC_NO_CONTENT) {
                log.info("成功");
            } else {
                throw new RuntimeException("申请账单异常, 响应码 = " + statusCode+ ", 申请账单返回结果 = " + bodyAsString);
            }

            //获取账单下载地址
            Gson gson = new Gson();
            Map<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
            return resultMap.get("download_url");

        } finally {
            response.close();
        }
    }
}
